package com.fount4j.generator.context;

import com.fount4j.generator.config.PropertyHolder;
import com.fount4j.generator.config.TableConfig;
import com.fount4j.generator.exception.GenerateException;
import com.fount4j.generator.introspector.DataSourceIntrospector;
import com.fount4j.generator.introspector.FileIntrospector;
import com.fount4j.generator.introspector.extend.*;
import com.fount4j.generator.util.BeanUtils;
import com.fount4j.generator.util.StringUtils;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.ResourceLoader;
import org.beetl.core.Template;
import org.beetl.core.resource.ClasspathResourceLoader;
import org.beetl.core.resource.FileResourceLoader;
import org.beetl.core.resource.WebAppResourceLoader;
import org.yaml.snakeyaml.Yaml;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * YAML 配置文件上下文
 *
 * @author Morven 2016/12/22
 */
public class YmlGeneratorContext extends GeneratorContext {

    public YmlGeneratorContext(String configPath) {
        try {
            loadConfiguration(new FileInputStream(configPath));
        } catch (FileNotFoundException e) {
            throw new GenerateException("找不到配置文件", e);
        } catch (IOException e) {
            throw new GenerateException("读取配置文件错误", e);
        }
    }

    public YmlGeneratorContext(File configFile) {
        try {
            loadConfiguration(new FileInputStream(configFile));
        } catch (FileNotFoundException e) {
            throw new GenerateException("找不到配置文件", e);
        }
    }

    public YmlGeneratorContext(InputStream configInputStream) {
        loadConfiguration(configInputStream);
    }

    protected void loadConfiguration(InputStream is) {
        Yaml yaml = new Yaml();
        Map<String, Object> configMap = (Map<String, Object>) yaml.load(is);
        parseClassLoader(configMap);
        parseDataSource(configMap);
        parseIntrospector(configMap);
        parseTemplate(configMap);
        parseTables(configMap);
        this.properties = configMap;
    }

    /**
     * 加载 ClassLoader
     *
     * @param configMap 配置文件对象
     */
    protected void parseClassLoader(Map<String, Object> configMap) {
        List<URL> urls = new ArrayList<>();
        File file;
        List<String> entries = (List<String>) configMap.remove("classPathEntry");
        if (entries != null) {
            for (String classPathEntry : entries) {
                file = new File(classPathEntry);
                if (!file.exists()) {
                    throw new GenerateException("找不到文件 classPathEntry: " + classPathEntry);
                }
                try {
                    urls.add(file.toURI().toURL());
                } catch (MalformedURLException e) {
                    throw new GenerateException("解析 classPathEntry 出错", e);
                }
            }
        }
        ClassLoader parent = Thread.currentThread().getContextClassLoader();
        this.classLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), parent);
    }

    /**
     * 解析 JDBC 配置
     *
     * @param configMap 要解析的配置
     */
    protected void parseDataSource(Map<String, Object> configMap) {
        Map<String, Object> dataSourceMap = (Map<String, Object>) configMap.remove("dataSource");
        Object introClsConfig = dataSourceMap.get("class");
        Class<? extends DataSourceIntrospector> introCls = null;
        if (introClsConfig == null) {
            introCls = JdbcDataSourceIntrospector.class;
        } else {
            try {
                introCls = (Class<? extends DataSourceIntrospector>) Class.forName(introClsConfig.toString());
            } catch (ClassNotFoundException e) {
                throw new GenerateException("没有找到数据源解析器类：" + introClsConfig, e);
            }
        }
        this.dataSourceIntrospector = parseConfigBean(introCls, dataSourceMap);
    }

    /**
     * 解析器配置
     *
     * @param configMap 要解析的配置
     */
    protected void parseIntrospector(Map<String, Object> configMap) {
        String tableIntrospectorConfig = (String) configMap.remove("tableIntrospector");
        if (StringUtils.isNotBlank(tableIntrospectorConfig)) {
            try {
                this.tableIntrospector = (TableIntrospector) BeanUtils.newInstance(tableIntrospectorConfig);
            } catch (Exception e) {
                throw new GenerateException("初始化 Table 解析器出错: " + tableIntrospectorConfig, e);
            }
        } else {
            this.tableIntrospector = new TableIntrospector();
        }

        String columnIntrospectorConfig = (String) configMap.remove("columnIntrospector");
        if (StringUtils.isNotBlank(columnIntrospectorConfig)) {
            try {
                columnIntrospector = (ColumnIntrospector) BeanUtils.newInstance(columnIntrospectorConfig);
            } catch (Exception e) {
                throw new GenerateException("初始化 Column 解析器出错: " + columnIntrospectorConfig, e);
            }
        } else {
            columnIntrospector = new ColumnIntrospector();
        }

        List introspectors = (List) configMap.remove("introspectors");
        if (introspectors != null && introspectors.size() > 0) {
            for (Object obj : introspectors) {
                Map<String, Object> map = (Map<String, Object>) obj;
                String type = (String) map.remove("type");
                if (StringUtils.isNotBlank(type)) {
                    if ("entity".equals(type)) {
                        EntityIntrospector entityIntrospector = parseConfigBean(EntityIntrospector.class, map);
                        if (StringUtils.isBlank(entityIntrospector.getInfoKey())) {
                            entityIntrospector.setInfoKey("entity");
                        }
                        this.introspectors.add(entityIntrospector);
                    } else if ("dao".equals(type)) {
                        DaoIntrospector daoIntrospector = parseConfigBean(DaoIntrospector.class, map);
                        if (StringUtils.isBlank(daoIntrospector.getInfoKey())) {
                            daoIntrospector.setInfoKey("dao");
                        }
                        this.introspectors.add(daoIntrospector);
                    } else if ("mapper".equals(type)) {
                        MyBatisMapperIntrospector mapperIntrospector = parseConfigBean(MyBatisMapperIntrospector.class,
                                map);
                        if (StringUtils.isBlank(mapperIntrospector.getInfoKey())) {
                            mapperIntrospector.setInfoKey("mapper");
                        }
                        this.introspectors.add(mapperIntrospector);
                    }
                } else {
                    String cls = (String) map.remove("class");
                    try {
                        FileIntrospector configBean = (FileIntrospector) parseConfigBean(Class.forName(cls), map);
                        this.introspectors.add(configBean);
                    } catch (ClassNotFoundException e) {
                        throw new GenerateException("没有找到解析器类：" + cls, e);
                    }
                }
            }
        }
    }

    /**
     * 解析模板配置
     *
     * @param configMap 要解析的配置
     */
    protected void parseTemplate(Map<String, Object> configMap) {
        if (introspectors == null || introspectors.isEmpty()) {
            return;
        }
        GroupTemplate gt;
        Map<String, Object> templateMap = (Map<String, Object>) configMap.remove("template");
        try {
            if (templateMap == null || templateMap.isEmpty()) {
                FileResourceLoader resourceLoader = new FileResourceLoader("./assets/template/");
                Configuration cfg = Configuration.defaultConfiguration();
                gt = new GroupTemplate(resourceLoader, cfg);
            } else {
                ResourceLoader resourceLoader = createResourceLoader(templateMap);
                Configuration cfg;
                String config = (String) templateMap.remove("config");
                if (StringUtils.isNotBlank(config)) {
                    cfg = new Configuration();
                    cfg.add(config);
                } else {
                    cfg = Configuration.defaultConfiguration();
                }
                gt = new GroupTemplate(resourceLoader, cfg);
            }
        } catch (IOException e) {
            throw new GenerateException("加载模板引擎配置出错", e);
        }
        for (FileIntrospector fi : introspectors) {
            if (fi.isGenerateEnable()) {
                String template;
                if (StringUtils.isNotBlank(fi.getTemplate())) {
                    template = fi.getTemplate();
                } else {
                    template = fi.getInfoKey() + ".btl";
                }
                Template tmp = gt.getTemplate(template);
                putTemplate(fi.getInfoKey(), tmp);
            }
        }
    }

    /**
     * 解析要生成的表
     *
     * @param configMap 要解析的配置
     */
    protected void parseTables(Map<String, Object> configMap) {
        tables = parseConfigBeanList(TableConfig.class, (List<Map<String, Object>>) configMap.remove("tables"));
    }

    /**
     * 创建模板引擎加载器
     *
     * @param templateMap 配置项
     * @return 模板引擎加载器
     */
    protected ResourceLoader createResourceLoader(Map<String, Object> templateMap) {
        ResourceLoader resourceLoader;
        String root = (String) templateMap.remove("root");
        if (StringUtils.isNotBlank(root)) {
            if (root.startsWith("classpath:")) {
                resourceLoader = new ClasspathResourceLoader(root.replaceFirst("classpath:", ""));
            } else if (root.startsWith("webapp:")) {
                resourceLoader = new WebAppResourceLoader(root.replaceFirst("webapp:", ""));
            } else {
                resourceLoader = new FileResourceLoader(root);
            }
        } else {
            resourceLoader = new ClasspathResourceLoader();
        }
        return resourceLoader;
    }

    /**
     * 将 List 类型的配置转为配置项集合
     *
     * @param configBeanClass 配置项类型
     * @param configList      配置集合
     * @param <T>             泛型
     * @return 配置项实体类的集合
     */
    protected <T> List<T> parseConfigBeanList(Class<T> configBeanClass, List<Map<String, Object>> configList) {
        if (configList == null || configList.isEmpty()) {
            return null;
        }
        List<T> list = new ArrayList<>();
        for (Map<String, Object> item : configList) {
            list.add(parseConfigBean(configBeanClass, item));
        }
        return list;
    }

    /**
     * 根据字段获取配置项的值
     *
     * @param field     字段
     * @param configMap 配置项
     * @return 配置项的值
     */
    protected Object getConfigValue(PropertyDescriptor field, Map<String, Object> configMap) {
        String name = field.getName();
        Object objVal = configMap.remove(name);
        if (objVal == null) {
            return null;
        }
        Class<?> fieldType = field.getPropertyType();
        if (!fieldType.equals(objVal.getClass())) {
            if (fieldType.equals(Long.class) || fieldType.equals(Long.TYPE)) {
                objVal = Long.parseLong(objVal.toString());
            } else if (fieldType.equals(Integer.class) || fieldType.equals(Integer.TYPE)) {
                objVal = Integer.parseInt(objVal.toString());
            } else if (fieldType.equals(Boolean.class) || fieldType.equals(Boolean.TYPE)) {
                objVal = Boolean.parseBoolean(objVal.toString());
            } else if (fieldType.equals(Float.class) || fieldType.equals(Float.TYPE)) {
                objVal = Float.parseFloat(objVal.toString());
            } else if (fieldType.equals(Double.class) || fieldType.equals(Double.TYPE)) {
                objVal = Double.parseDouble(objVal.toString());
            } else if (List.class.equals(fieldType) && objVal instanceof List) {
                // do nothing
            } else if (Map.class.equals(fieldType) && objVal instanceof Map) {
                // do nothing
            } else {
                throw new GenerateException("无法解析的配置项类型：" + name);
            }
        }
        return objVal;
    }

    /**
     * 将 Map 类型的配置值转为配置实体类对象
     *
     * @param configBeanClass 配置实体类型
     * @param configMap       配置项
     * @param <T>             泛型
     * @return 实体类对象
     * @throws GenerateException 异常
     */
    protected <T> T parseConfigBean(Class<T> configBeanClass, Map<String, Object> configMap) {
        T newInstance;
        try {
            newInstance = configBeanClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new GenerateException("创建配置类对象出错", e);
        }
        if (configMap == null) {
            return newInstance;
        }

        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(configBeanClass);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor pd : propertyDescriptors) {
                Method writeMethod = pd.getWriteMethod();
                if (writeMethod != null) {
                    Object objVal = getConfigValue(pd, configMap);
                    if (objVal != null) {
                        writeMethod.invoke(newInstance, objVal);
                    }
                }
            }
        } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
            throw new GenerateException("解析配置出错", e);
        }
        if (newInstance instanceof PropertyHolder) {
            parseProperties((PropertyHolder) newInstance, configMap);
        }
        return newInstance;
    }

    /**
     * 解析其他参数
     *
     * @param propertyHolder 各类配置实体
     * @param configMap      从文件中读取到的配置
     */
    protected void parseProperties(PropertyHolder propertyHolder, Map<String, Object> configMap) {
        propertyHolder.setProperties(configMap);
    }

}
